﻿using PuppeteerSharp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.Metadata;
using System.Security.AccessControl;
using System.Text;
using System.Threading.Tasks;
using ResourceType = PuppeteerSharp.ResourceType;

namespace Q.Spider.Core
{
    public class Engine : IDisposable
    {
        private readonly QSpiderCore _Core;

        public Engine(QSpiderCore core)
        {
            _Core = core;
        }

        public IBrowser? Browser { get; set; }
        public IPage? CurrentPage { get; set; }

        /// <summary>
        /// 引擎初始化
        /// </summary>
        public async Task Init()
        {


            if (Browser == null)
            {
                Browser = await Puppeteer.LaunchAsync(_Core.Config.PuppeteerLaunchOptions);


                CurrentPage = (await Browser.PagesAsync())[0];
                await CurrentPage.EvaluateExpressionOnNewDocumentAsync("Object.defineProperty(navigator, \"webdriver\", {get: () => undefined})");
                // 根据配置设置  如果是false 不加载对应某些资源
                if (!_Core.Config.EngineIsLoadImage)
                {
                    await CurrentPage.SetRequestInterceptionAsync(true);
                    CurrentPage.Request += CurrentPage_Request;
                }

            }
        }

        /// <summary>
        /// 开始加载页面
        /// </summary>
        /// <returns></returns>
        public async Task StartLoad(string url, int? timeout = 30000)
        {
            await Init();

            await CurrentPage.SetUserAgentAsync("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3477.0 Safari/537.36");


            // 指定页面打开某个页面
            _ = await CurrentPage.GoToAsync(url, timeout, new WaitUntilNavigation[]
                { WaitUntilNavigation.DOMContentLoaded });


            await LimitCheckAsync();

        }



        public async Task<string> GetHtmlAsync(string cssSelector)
        {
            if (Browser == null)
            {
                throw new Exception("请先访问页面");
            }

            return await CurrentPage.EvaluateExpressionAsync<string>
                   ($"document.querySelector('{cssSelector}').innerHTML");
        }

        public async Task<string> GetHtmlAsync()
        {
            if (Browser == null)
            {
                throw new Exception("请先访问页面");
            }
            return await CurrentPage.GetContentAsync();
        }


        public async Task LimitCheckAsync()
        {
            if (Browser == null)
            {
                throw new Exception("请先访问页面");
            }
            var html = await GetHtmlAsync();
            if (html.Contains("been clicking on the same link a LOT"))
            {
                throw new LimitException("link a LOT");
            }

            if (html.Contains("Access Denied"))
            {
                throw new LimitException("Access Denied");
            }
        }



        /// <summary>
        /// 滚动到底部,不会用这个扣40
        /// </summary>
        /// <param name="delayTime">延迟时间</param>
        /// <param name="delayAction">延迟Action ， 如果有的页面需要等待ajax dom加载完毕再滚动需要传此参数</param>
        /// <returns></returns>
        public async Task ScrollToBottomAsync(int delayTime = 500, Action delayAction = null)
        {
            var top = 0;
            var tryCount = 0;
            while (true)
            {
                await CurrentPage.Keyboard.PressAsync("PageDown");
                delayAction?.Invoke();
                await Task.Delay(delayTime);
                var currentTop = await CurrentPage.EvaluateExpressionAsync<int>("document.documentElement.scrollTop");
                if (top == currentTop)
                {
                    if (tryCount++ == 3)
                    {
                        break;
                    }
                }
                top = currentTop;
            }
        }


        public async Task SimulateJsClick(string cssSelector)
        {
            var js = $"document.querySelector('{cssSelector}').click()";
            await CurrentPage.EvaluateExpressionAsync(js);
        }

        /// <summary>
        /// 获取dom的BoundingClientRect
        /// 一般用于dom定位
        /// </summary>
        /// <param name="cssSelector"></param>
        /// <returns></returns>
        public async Task<Models.BoundingClientRect> GetBoundingClientRect(string cssSelector)
        {
            var dom = await CurrentPage.QuerySelectorAsync(cssSelector);
            var rect = await dom.EvaluateFunctionAsync<Models.BoundingClientRect>("(el)=>{ var {x,y,top,bottom,height,width} = el.getBoundingClientRect();return {x,y,top,bottom,height,width}; }");
            return rect;
        }

        /// <summary>
        /// 获取所有链接 A标签的 Href
        /// </summary>
        /// <param name="cssSelector">css选择器，注意不要传a。 只需要传要找a标签的父级元素</param>
        /// <returns></returns>
        public async Task<List<string>> GetAllLikeAsync(string cssSelector)
        {
            return await CurrentPage.EvaluateExpressionAsync<List<string>>
                   ($"Array.from(document.querySelectorAll('{cssSelector} a')).map(a => a.href)");
        }



        /// <summary>
        /// 根据css样式 获取符合要求的所有dom
        /// </summary>
        /// <param name="cssSelector">css选择器，注意不要传a。 只需要传要找a标签的父级元素</param>
        /// <returns></returns>
        public async Task<List<string>> GetAllDomAsync(string cssSelector)
        {
            return await CurrentPage.EvaluateExpressionAsync<List<string>>
                   ($"Array.from(document.querySelectorAll('{cssSelector} a')).map(a => a.href)");
        }






        /// <summary>
        /// 获取Dom 文本内容
        /// </summary>
        /// <param name="cssSelector">css选择器 可使用伪类:如:nth-child(2)</param>
        /// <param name="attr">属性名</param>
        /// <returns>如果未找到dom则会返回字符串</returns>
        public async Task<string> GetDomTextAsync(string cssSelector)
        {
            return await GetDomAttrAsync(cssSelector, "innerText");
        }
        /// <summary>
        /// 获取Dom HTML
        /// </summary>
        /// <param name="cssSelector"></param>
        /// <param name="attr"></param>
        /// <returns></returns>
        public async Task<string> GetDomHtmlAsync(string cssSelector)
        {
            return await GetDomAttrAsync(cssSelector, "innerHTML");
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="cssSelector">css选择器 可使用伪类:如:nth-child(2)</param>
        /// <param name="attr">属性名</param>
        /// <returns>如果未找到dom则会返回字符串</returns>
        public async Task<string> GetDomAttrAsync(string cssSelector, string attr)
        {
            var js = $"document.querySelector(\"{cssSelector}\") ? document.querySelector(\"{cssSelector}\")['{attr}'] : ''";
            return await CurrentPage.EvaluateExpressionAsync<string>(js);
        }
        public async Task<string> GetDomAttrAsync(string cssSelector, string attr, string attr1)
        {
            var js = $"document.querySelector(\"{cssSelector}\") ? document.querySelector(\"{cssSelector}\")['{attr}'].{attr1} : ''";
            return await CurrentPage.EvaluateExpressionAsync<string>(js);
        }

        /// <summary>
        /// 获取Dom的属性
        /// </summary>
        /// <param name="dom"> dom的句柄 </param>
        /// <param name="attr">需要的属性</param>
        /// <returns></returns>
        public async Task<string> GetDomAttrAsync(ElementHandle dom, string attr)
        {

            var js = "(dom,attr)=> dom[attr]";
            if (attr.StartsWith("data-"))
            {
                js = "(dom,attr)=> dom.dataset[attr.replace('data-','')]";
            }
            return await CurrentPage.EvaluateFunctionAsync<string>(js, dom, attr);
        }

        /// <summary>
        /// 判断js对象是否为null 或者Undefined
        /// </summary>
        /// <param name="jsobj"></param>
        /// <returns></returns>
        public async Task<bool> GetJsObjIsNullOrUndefined(string jsobj, string property)
        {
            string js = $"!{jsobj}.hasOwnProperty('{property}')";
            return await CurrentPage.EvaluateExpressionAsync<bool>
                (js);
        }

        /// <summary>
        /// 等待dom加载完成
        /// </summary>
        /// <param name="selector">css选择器</param>
        /// <param name="options"></param>
        /// <returns></returns>
        public async Task<IElementHandle> WaitForSelectorAsync(string selector, WaitForSelectorOptions options = null)
        {
            return await CurrentPage.WaitForSelectorAsync(selector, options ?? new WaitForSelectorOptions());
        }


        /// <summary>
        /// 根据css 查询所有符合的句柄 如果没有返回0 
        /// </summary>
        /// <param name="selector">css选择器</param>
        /// <returns>所有符合的句柄</returns>
        public Task<IElementHandle[]> QuerySelectorAllAsync(string selector)
        {
            return CurrentPage.QuerySelectorAllAsync(selector);
        }
        /// <summary>
        /// 根据css 查询符合的句柄 如果没有返回null
        /// </summary>
        /// <param name="selector">css选择器</param>
        /// <returns>所有符合的句柄</returns>
        public Task<IElementHandle> QuerySelectorAsync(string selector)
        {
            return CurrentPage.QuerySelectorAsync(selector);
        }
        /// <summary>
        /// 等待执行 js 代码
        /// </summary>
        /// <param name="script">js代码</param>
        /// <returns>JShandle</returns> jshandle 是个什么
        public Task<IJSHandle> WaitForFunctionAsync(string script, params object[] args)
        {
            return CurrentPage.WaitForFunctionAsync(script, args);
        }


        /// <summary>
        /// 注册Response捕获
        /// 返回的是一个Task
        /// </summary>
        /// <param name="funcAsync"></param>
        /// <returns></returns>
        public Task<IResponse> RegisterResponseCapture(Func<IResponse, Task<bool>> funcAsync)
        {
            var responseTcs = new TaskCompletionSource<IResponse>
                          (TaskCreationOptions.RunContinuationsAsynchronously);

            //这个类 异步变同步

            //response 返回事件处理
            async void responseEventListener(object sender, ResponseCreatedEventArgs e)
            {
                //如果满足条件
                if (await funcAsync(e.Response))
                {
                    //清除事件
                    CurrentPage.Response -= responseEventListener;
                    //设置结果
                    responseTcs.SetResult(e.Response);
                }
            }
            //注册事件
            CurrentPage.Response += responseEventListener;

            return responseTcs.Task;
        }
        /// <summary>
        /// 禁止加载一些资源
        /// </summary>
        private async void CurrentPage_Request(object sender, RequestEventArgs e)
        {
            if (e.Request.ResourceType == ResourceType.Image
                || e.Request.ResourceType == ResourceType.Img
                || e.Request.ResourceType == ResourceType.Font
                || e.Request.ResourceType == ResourceType.Media)
            {
                await e.Request.AbortAsync();
            }
            else
            {
                await e.Request.ContinueAsync();
            }
        }

        public async void Dispose()
        {
            CurrentPage?.Dispose();
            if (Browser != null)
            {
                await Browser?.CloseAsync();
                Browser?.Dispose();
            }
        }


    }
}
